# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo.fields import Command
from odoo.tests import Form, tagged

from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon


@tagged('post_install', '-at_install')
class TestSaleCouponProgramRules(TestSaleCouponCommon):

    @classmethod
    def setUpClass(cls):
        super().setUpClass()

        cls.iPadMini = cls.env['product.product'].create({'name': 'Large Cabinet', 'list_price': 320.0})
        tax_15pc_excl = cls.env['account.tax'].create({
            'name': "15% Tax excl",
            'amount_type': 'percent',
            'amount': 15,
        })
        cls.product_delivery_poste = cls.env['product.product'].create({
            'name': 'The Poste',
            'type': 'service',
            'categ_id': cls.env.ref('delivery.product_category_deliveries').id,
            'sale_ok': False,
            'purchase_ok': False,
            'list_price': 20.0,
            'taxes_id': [(6, 0, [tax_15pc_excl.id])],
        })
        cls.carrier = cls.env['delivery.carrier'].create({
            'name': 'The Poste',
            'fixed_price': 20.0,
            'delivery_type': 'base_on_rule',
            'product_id': cls.product_delivery_poste.id,
        })
        cls.env['delivery.price.rule'].create([{
            'carrier_id': cls.carrier.id,
            'max_value': 5,
            'list_base_price': 20,
        }, {
            'carrier_id': cls.carrier.id,
            'operator': '>=',
            'max_value': 5,
            'list_base_price': 50,
        }, {
            'carrier_id': cls.carrier.id,
            'operator': '>=',
            'max_value': 300,
            'variable': 'price',
            'list_base_price': 0,
        }])


    # Test a free shipping reward + some expected behavior
    # (automatic line addition or removal)

    def test_free_shipping_reward(self):
        # Test case 1: The minimum amount is not reached, the reward should
        # not be created
        self.immediate_promotion_program.active = False
        program = self.env['loyalty.program'].create({
            'name': 'Free Shipping if at least 100 euros',
            'trigger': 'auto',
            'rule_ids': [(0, 0, {
                'minimum_amount': 100,
                'minimum_amount_tax_mode': 'incl',
            })],
            'reward_ids': [(0, 0, {
                'reward_type': 'shipping',
            })],
        })

        order = self.empty_order

        # Price of order will be 5*1.15 = 5.75 (tax included)
        order.write({'order_line': [
            (0, False, {
                'product_id': self.product_B.id,
                'name': 'Product B',
                'product_uom_qty': 1.0,
            })
        ]})
        self._auto_rewards(order, program)
        self.assertEqual(len(order.order_line.ids), 1)

        # I add delivery cost in Sales order
        delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({
            'default_order_id': order.id,
            'default_carrier_id': self.env['delivery.carrier'].search([])[1]
        }))
        choose_delivery_carrier = delivery_wizard.save()
        choose_delivery_carrier.button_confirm()

        self._auto_rewards(order, program)
        self.assertEqual(len(order.order_line.ids), 2)

        # Test Case 1b: amount is not reached but is on a threshold
        # The amount of deliverable product + the one of the delivery exceeds the minimum amount
        # yet the program shouldn't be applied
        # Order price will be 5.75 + 81.74*1.15 = 99.75
        order.write({'order_line': [
            (0, False, {
                'product_id': self.product_B.id,
                'name': 'Product 1B',
                'product_uom_qty': 1.0,
                'price_unit': 81.74,
            })
        ]})
        self._auto_rewards(order, program)
        self.assertEqual(len(order.order_line.ids), 3)

        # Test case 2: the amount is sufficient, the shipping should
        # be reimbursed
        order.write({'order_line': [
            (0, False, {
                'product_id': self.product_A.id,
                'name': 'Product 1',
                'product_uom_qty': 1.0,
                'price_unit': 0.30,
            })
        ]})

        self._auto_rewards(order, program)
        self.assertEqual(len(order.order_line.ids), 5)

        # Test case 3: the amount is not sufficient now, the reward should be removed
        order.write({'order_line': [
            (2, order.order_line.filtered(lambda line: line.product_id.id == self.product_A.id).id, False)
        ]})
        self._auto_rewards(order, program)
        self.assertEqual(len(order.order_line.ids), 3)

    def test_shipping_cost(self):
        # Free delivery should not be taken into account when checking for minimum required threshold
        p_minimum_threshold_free_delivery = self.env['loyalty.program'].create({
            'name': 'free shipping if > 872 tax excl',
            'trigger': 'auto',
            'rule_ids': [(0, 0, {
                'minimum_amount': 872,
            })],
            'reward_ids': [(0, 0, {
                'reward_type': 'shipping',
            })]
        })
        p_2 = self.env['loyalty.program'].create({
            'name': '10% reduction if > 872 tax excl',
            'trigger': 'auto',
            'rule_ids': [(0, 0, {
                'minimum_amount': 872,
            })],
            'reward_ids': [(0, 0, {
                'reward_type': 'discount',
                'discount': 10,
                'discount_mode': 'percent',
                'discount_applicability': 'order',
            })]
        })
        programs = (p_minimum_threshold_free_delivery | p_2)
        order = self.empty_order
        self.iPadMini.taxes_id = self.tax_10pc_incl
        sol1 = self.env['sale.order.line'].create({
            'product_id': self.iPadMini.id,
            'name': 'Large Cabinet',
            'product_uom_qty': 3.0,
            'order_id': order.id,
        })
        self._auto_rewards(order, programs)
        self.assertEqual(len(order.order_line.ids), 3, "We should get the 10% discount line since we bought 872.73$ and a free shipping line with a value of 0")
        self.assertEqual(order.order_line.filtered(lambda l: l.reward_id.reward_type == 'shipping').price_unit, 0)
        self.assertEqual(order.amount_total, 960 * 0.9)
        order.carrier_id = self.env['delivery.carrier'].search([])[1]

        # I add delivery cost in Sales order
        delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({
            'default_order_id': order.id,
            'default_carrier_id': self.env['delivery.carrier'].search([])[1]
        }))
        choose_delivery_carrier = delivery_wizard.save()
        choose_delivery_carrier.button_confirm()

        self._auto_rewards(order, programs)
        self.assertEqual(len(order.order_line.ids), 4, "We should get both rewards regardless of applying order.")

        p_minimum_threshold_free_delivery.sequence = 10
        (order.order_line - sol1).unlink()
        # I add delivery cost in Sales order
        delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({
            'default_order_id': order.id,
            'default_carrier_id': self.env['delivery.carrier'].search([])[1]
        }))
        choose_delivery_carrier = delivery_wizard.save()
        choose_delivery_carrier.button_confirm()
        self._auto_rewards(order, programs)
        self.assertEqual(len(order.order_line.ids), 4, "We should get both rewards regardless of applying order.")

    def test_shipping_cost_numbers(self):
        # Free delivery should not be taken into account when checking for minimum required threshold
        p_1 = self.env['loyalty.program'].create({
            'name': 'Free shipping if > 872 tax excl',
            'trigger': 'with_code',
            'rule_ids': [(0, 0, {
                'mode': 'with_code',
                'code': 'free_shipping',
                'minimum_amount': 872,
            })],
            'reward_ids': [(0, 0, {
                'reward_type': 'shipping',
            })],
        })
        p_2 = self.env['loyalty.program'].create({
            'name': 'Buy 4 large cabinet, get one for free',
            'trigger': 'auto',
            'rule_ids': [(0, 0, {
                'product_ids': self.iPadMini,
                'minimum_qty': 4,
            })],
            'reward_ids': [(0, 0, {
                'reward_type': 'product',
                'reward_product_id': self.iPadMini.id,
                'reward_product_qty': 1,
                'required_points': 1,
            })],
        })
        programs = (p_1 | p_2)
        order = self.empty_order
        self.iPadMini.taxes_id = self.tax_10pc_incl
        sol1 = self.env['sale.order.line'].create({
            'product_id': self.iPadMini.id,
            'name': 'Large Cabinet',
            'product_uom_qty': 3.0,
            'order_id': order.id,
        })

        # I add delivery cost in Sales order
        delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({
            'default_order_id': order.id,
            'default_carrier_id': self.carrier.id
        }))
        choose_delivery_carrier = delivery_wizard.save()
        choose_delivery_carrier.button_confirm()
        self._auto_rewards(order, programs)
        self.assertEqual(len(order.order_line.ids), 2)
        self.assertEqual(order.reward_amount, 0)
        # Shipping is 20 + 15%tax
        self.assertEqual(sum([line.price_total for line in order._get_no_effect_on_threshold_lines()]), 23)
        self.assertEqual(order.amount_untaxed, 872.73 + 20)

        self._apply_promo_code(order, 'free_shipping')
        self._auto_rewards(order, programs)
        self.assertEqual(len(order.order_line.ids), 3, "We should get the delivery line and the free delivery since we are below 872.73$")
        self.assertEqual(order.reward_amount, -20)
        self.assertEqual(sum([line.price_total for line in order._get_no_effect_on_threshold_lines()]), 0)
        self.assertEqual(order.amount_untaxed, 872.73)

        sol1.product_uom_qty = 4
        self._auto_rewards(order, programs)
        self.assertEqual(len(order.order_line.ids), 4, "We should get a free Large Cabinet")
        self.assertEqual(order.reward_amount, -20 - 320)
        self.assertEqual(sum([line.price_total for line in order._get_no_effect_on_threshold_lines()]), 0)
        self.assertEqual(order.amount_untaxed, 1163.64)

        programs |= self.env['loyalty.program'].create({
            'name': '20% reduction on large cabinet in cart',
            'trigger': 'auto',
            'rule_ids': [(0, 0, {})],
            'reward_ids': [(0, 0, {
                'reward_type': 'discount',
                'discount': 20,
                'discount_mode': 'percent',
                'discount_applicability': 'cheapest',
            })]
        })
        self._auto_rewards(order, programs)
        # 872.73 - (20% of 1 iPad) = 872.73 - 58.18 = 814.55
        self.assertAlmostEqual(order.amount_untaxed, 1105.45, 2, "One large cabinet should be discounted by 20%")

    def test_free_shipping_reward_last_line(self):
        """
            The free shipping reward cannot be removed if it is the last item in the sale order.
            However, we calculate its sequence so that it is the last item in the sale order.
            This can create an error if a default sequence is not determined.
        """
        self.immediate_promotion_program.active = False
        # Create a loyalty program
        loyalty_program = self.env['loyalty.program'].create({
            'name': 'GIFT Free Shipping',
            'program_type': 'loyalty',
            'applies_on': 'both',
            'trigger': 'auto',
            'rule_ids': [(0, 0, {
                    'reward_point_mode': 'money',
                    'reward_point_amount': 1,
                })],
            'reward_ids': [(0, 0, {
                'reward_type': 'shipping',
                'required_points': 100,
            })],
        })
        # Add points to a partner to trigger the promotion
        self.env['loyalty.card'].create({
            'program_id': loyalty_program.id,
            'partner_id': self.partner.id,
            'points': 250,
        })
        order = self.empty_order
        # Check if we can claim the free shipping reward
        order._update_programs_and_rewards()
        claimable_rewards = order._get_claimable_rewards()
        self.assertEqual(len(claimable_rewards), 1)
        # Try to apply the loyalty card to the sale order
        self.assertTrue(self._claim_reward(order, loyalty_program))
        # Check if there is an error in the sequence
        # via `_apply_program_reward` in `_claim_reward` method

    def test_nothing_delivered_nothing_to_invoice(self):
        program = self.env['loyalty.program'].create({
            'name': '10% reduction on all orders',
            'trigger': 'auto',
            'program_type': 'promotion',
            'rule_ids': [Command.create({
                'minimum_amount': 50,
            })],
            'reward_ids': [Command.create({
                'reward_type': 'discount',
                'discount': 10,
                'discount_mode': 'percent',
                'discount_applicability': 'order',
            })]
        })
        product = self.env['product.product'].create({
            'name': 'Test product',
            'type': 'consu',
            'list_price': 200.0,
            'invoice_policy': 'delivery',
        })
        order = self.empty_order
        self.env['sale.order.line'].create({
            'product_id': product.id,
            'order_id': order.id,
        })
        self._auto_rewards(order, program)
        self.assertNotEqual(order.reward_amount, 0)
        self.assertEqual(order.invoice_status, 'no')
        delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({
            'default_order_id': order.id,
            'default_carrier_id': self.carrier.id
        }))
        choose_delivery_carrier = delivery_wizard.save()
        choose_delivery_carrier.button_confirm()
        order.action_confirm()
        self.assertEqual(order.delivery_set, True)
        self.assertEqual(order.invoice_status, 'no')

    def test_delivery_shant_count_toward_quantity_bought(self):

        # Create promotion: 10% for everything
        discount_program = self.env['loyalty.program'].create({
            'name': '10 percent off order with min. 2 products',
            'trigger': 'auto',
            'program_type': 'promotion',
            'applies_on': 'current',
            'rule_ids': [(0, 0, {
                'minimum_qty': 2,
                'minimum_amount':0,
            })],
           'reward_ids': [(0, 0, {
                'reward_type': 'discount',
                'discount_mode': 'percent',
                'discount': 10.0,
                'discount_applicability': 'order',
            })],
        })

        # Create an order including: product and delivery
        order = self.empty_order
        self.env['sale.order.line'].create({
            'product_id': self.iPadMini.id,
            'name': self.iPadMini.name,
            'product_uom_qty': 1.0,
            'order_id': order.id,
        })
        self.env['sale.order.line'].create({
            'product_id': self.product_delivery_poste.id,
            'name': 'Free delivery charges\nFree Shipping',
            'product_uom_qty': 1.0,
            'order_id': order.id,
            'is_delivery': True,
        })

        # Calculate promotions
        self._auto_rewards(order, discount_program)

        # Make sure the promotion is NOT added
        err_msg = "No reward lines should be created as the delivery line shouldn't be included in the promotion calculation"
        self.assertEqual(len(order.order_line.ids), 2, err_msg)

    def test_free_shipping_should_be_removed_when_rules_are_not_met(self):
        p_1 = self.env['loyalty.program'].create({
            'name': 'Free shipping if > 872 tax excl',
            'trigger': 'with_code',
            'rule_ids': [(0, 0, {
                'mode': 'with_code',
                'code': 'free_shipping',
                'minimum_amount': 872,
            })],
            'reward_ids': [(0, 0, {
                'reward_type': 'shipping',
            })],
        })
        programs = (p_1)
        order = self.empty_order
        self.iPadMini.taxes_id = self.tax_10pc_incl
        sol1 = self.env['sale.order.line'].create({
            'product_id': self.iPadMini.id,
            'name': 'Large Cabinet',
            'product_uom_qty': 3.0,
            'order_id': order.id,
        })

        # I add delivery cost in Sales order
        delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({
            'default_order_id': order.id,
            'default_carrier_id': self.carrier.id
        }))
        choose_delivery_carrier = delivery_wizard.save()
        choose_delivery_carrier.button_confirm()
        self._auto_rewards(order, programs)
        self.assertEqual(len(order.order_line.ids), 2)
        self.assertEqual(order.reward_amount, 0)
        # Shipping is 20 + 15%tax
        self.assertEqual(sum(line.price_total for line in order._get_no_effect_on_threshold_lines()), 23)
        self.assertEqual(order.amount_untaxed, 872.73 + 20)

        self._apply_promo_code(order, 'free_shipping')
        self._auto_rewards(order, programs)
        self.assertEqual(len(order.order_line.ids), 3, "We should get the delivery line and the free delivery since we are below 872.73$")
        self.assertEqual(order.reward_amount, -20)
        self.assertEqual(sum(line.price_total for line in order._get_no_effect_on_threshold_lines()), 0)
        self.assertEqual(order.amount_untaxed, 872.73)

        sol1.product_uom_qty = 1
        self._auto_rewards(order, programs)
        self.assertEqual(len(order.order_line.ids), 2, "We should loose the free delivery reward since we are above 872.73$")
        self.assertEqual(order.reward_amount, 0)

    def test_discount_reward_claimable_when_shipping_reward_already_claimed_from_same_coupon(self):
        """
        Check that a discount reward is still claimable after the shipping reward is claimed.
        """
        program = self.env['loyalty.program'].create({
            'name': "10% Discount & Shipping",
            'applies_on': 'current',
            'trigger': 'with_code',
            'program_type': 'promotion',
            'rule_ids': [Command.create({'mode': 'with_code', 'code': "10PERCENT&SHIPPING"})],
            'reward_ids': [
                Command.create({
                    'reward_type': 'shipping',
                    'reward_product_qty': 1,
                }),
                Command.create({
                    'reward_type': 'discount',
                    'discount': 10,
                    'discount_mode': 'percent',
                    'discount_applicability': 'specific',
                }),
            ],
        })

        coupon = self.env['loyalty.card'].create({
            'program_id': program.id, 'points': 20, 'code': 'GIFT_CARD'
        })

        order = self.env['sale.order'].create({
            'partner_id': self.partner.id,
            'order_line': [Command.create({'product_id': self.product_B.id})]
        })

        ship_reward = program.reward_ids.filtered(lambda reward: reward.reward_type == 'shipping')
        discount_reward = program.reward_ids - ship_reward
        order._apply_program_reward(ship_reward, coupon)
        rewards = order._get_claimable_rewards()[coupon]
        msg = "The discount reward should still be applicable as only the shipping one was claimed."
        self.assertEqual(rewards, discount_reward, msg)
